#include "hashtables.h"
#include <string.h>

/*typedef struct user{
	int id;
	char name[32];
	int age;
}user;


user* user_new(void)
{
	return (malloc (sizeof (user)));
}

int compare (void* E1, void* E2)
{
	user* e1 = E1;
	const char*	key = E2;

	return (strcmp (e1->name, key));
}

int getHash (HashTable* htb, void* element)
{
	user *e = element;
	return (hash_string (e->name, htb->size));
}

int Add (HashTable *htb, const char* name, int age)
{
 user* usr = NULL;

	usr = user_new();
	strcpy(usr->name, name);
	usr->age = age;
	usr->id = hash_string (usr->name, htb->size);

	if (HashTable_Insert (htb, usr, (void*)usr->name) != 0){
		free (usr);
		return -1;
	}

 return 0;
}

int main (void)
{

 HashTable *htb = NULL;
 int i;
 user* usr;

for (i=0; i<1000; i++)
{
	htb = HashTable_New (5);
	HashTable_SetMethods (htb, compare, getHash);

	Add (htb, "A", 21);
	Add (htb, "A", 19);
	Add (htb, "B", 19);
	Add (htb, "C", 19);
	Add (htb, "D",12);
	Add (htb, "E",28);
	Add (htb, "F",40);

	HashTable_Remove (htb, "A");

	for (usr = HashTable_InitIter (htb); usr != NULL; usr = HashTable_IterNext(htb))
		printf ("iter1: %s\n", usr->name);


	usr = HashTable_Find (htb, "B");
	if (usr != NULL) printf ("FOUND USER: id: %d Name: %s age: %d\n", usr->id, usr->name, usr->age);
	else printf ("not found\n");

	

	printf ("____________________ITER_________________________\n");
	for (usr = HashTable_InitIter (htb); usr != NULL; usr = HashTable_IterNext(htb))
		printf ("iter2: %s\n", usr->name);
	printf ("_________________________________________________\n");	
	printf ("htb->counter = %d\n", htb->counter);

	HashTable_Destroy (&htb);
}

 return 0;
}
*/
int hash_string (const char *string, int MAX)
{
	unsigned int hash = 5381; /* DJB Hash */
	const char *s = NULL;

	for (s = string; *s; s++){
		hash = ((hash << 5) + hash) + tolower(*s);
	}

 return (hash & 0x7FFFFFFF) % MAX;
}

HashTable* HashTable_New (unsigned short size)
{
	HashTable *new = NULL;
	int i;

	new = malloc (sizeof (*new));
	if (new != NULL)
	{
		new->indexes = malloc (sizeof (List*) * size);
		if (new->indexes != NULL)
		{
			new->counter = 0;
			new->size = size;
			new->iter = NULL;
			for (i=0; i < size; i++)
			{
				new->indexes[i] = NULL;
			}
			memset (new->indexes, 0, sizeof (new->indexes));
			new->Table = List_New();
			if (new->Table == NULL)
			{
				free (new);
				new = NULL;
			}
		}
		else
		{
			free (new);
			new = NULL;
		}
	}

 return (new);
}

void HashTable_SetMethods (HashTable* this, int (*compare)(void* E1, void* E2),
							int (*getHash)(HashTable *this, void* E))
{
	this->compare = compare;
	this->getHash = getHash;
}

void*  HashTable_InitIter		(HashTable* this)
{
	if (this != NULL)
	{
		this->CurrList = List_InitIter(this->Table);
		this->iter = List_InitIter (this->CurrList);
		return (this->iter);
	}
 return NULL;
}

void* HashTable_IterNext		(HashTable* this)
{
	void *E = NULL;

	if (this != NULL)
	{
		E = List_IterNext (this->CurrList);
		if (E == NULL)
		{
			this->CurrList = List_IterNext (this->Table);
			E = List_InitIter (this->CurrList);/*!!!!*/
		}
	}

 return (E);
}

int	  HashTable_Insert			(HashTable* this, void* element, const char* key)
{
	int hash = this->getHash (this, element);
	List* new = NULL;
	void *E = NULL;

	if (this != NULL)
	{
		if (this->indexes[hash] == NULL) /* if there's no collision then create the element */
		{
			new = List_New ();
			if (new == NULL)
				return -1;

			List_PushTail (new, LIST_GENERIC(element)); /* insert the element to the created list */
			this->indexes[hash] = new; /* make the hash case pointing to the newely created list */
			List_PushTail (this->Table, LIST_GENERIC(new)); /* insert the created list to the table */
		}
		else /* collisions !! */
		{
			LIST_FOREACH (E, this->indexes[hash])
			{
				if (this->compare (E, (void*)key) == 0)
					return -1;
			}
			List_PushTail (this->indexes[hash], LIST_GENERIC(element)); /* insert the element to the list */
		}
	}
	else
	{
		return -1;
	}

	this->counter ++;
	return 0;
}

void* HashTable_Find (HashTable* this, const char* key)
{
	int hash = hash_string(key, this->size);
	void* E = NULL;

	LIST_FOREACH (E, this->indexes[hash])
	{
		if (this->compare (E, (void*)key) == 0)
			return (E);
	}

 return (NULL);
}

int HashTable_Remove (HashTable *this, const char* key)
{
	int hash = hash_string(key, this->size);
	void* E = NULL;

	LIST_FOREACH (E, this->indexes[hash])
	{
		if (this->compare (E, (void*)key) == 0)
		{
			List_Del (this->indexes[hash], &this->indexes[hash]->iter);
			if (this->indexes[hash]->iter == NULL)
			if (this->indexes[hash]->counter == 0)
			{
				LIST_FOREACH (E, this->Table)
				{
					if (E == this->indexes[hash]){
						List_Del (this->Table, &this->Table->iter);
						this->indexes[hash] = NULL;
					}
				}
			}
			this->counter --;
			return 0;
		}
	}
 return -1;
}

void* HashTable_Pop (HashTable *this, const char* key)
{
	int hash = hash_string(key, this->size);
	void* E = NULL,
		 *elm = NULL;

	LIST_FOREACH (E, this->indexes[hash])
	{
		if (this->compare (E, (void*)key) == 0)
		{
			elm = List_Pop (this->indexes[hash], &this->indexes[hash]->iter);
			if (this->indexes[hash]->iter == NULL)
			if (this->indexes[hash]->counter == 0)
			{
				LIST_FOREACH (E, this->Table)
				{
					if (E == this->indexes[hash]){
						List_Del (this->Table, &this->Table->iter);
						this->indexes[hash] = NULL;
					}
				}
			}
			this->counter --;
			break;
		}
	}
 return elm;
}

void HashTable_Destroy (HashTable** hashtable)
{
	Data *data = NULL, *temp = NULL;
	int i;

	for (i=0; i<(*hashtable)->size; i++)
	{
		if ((*hashtable)->indexes[i] != NULL)
		{
			/* for each element : this->EClear (element);*/
			List_Destroy(&(*hashtable)->indexes[i]);
		}
	}

	data = (*hashtable)->Table->head;
	while (data != NULL)
	{
		temp = data;
		data = data->next;
		free (temp);
	}

	free ((*hashtable)->Table);
	free ((*hashtable)->indexes);
	free (*hashtable);
	hashtable = NULL;
}

